﻿using System.Data;
using System.Text;

namespace System
{
    public static partial class Utility
    {
        /// <summary>
        ///  Used to get recursively down
        /// </summary>
        /// <param name="pDataTable">DataTable object to be filtered</param>
        /// <param name="pRootValue">Root value</param>
        /// <param name="pChildColName">Name of chiled column</param>
        /// <param name="pParentColName">Name of parent column</param>
        /// <param name="pAddNodeLevel">If set to <c>true</c> [p add node level].</param>
        /// <param name="pMaxDepth">Max depth</param>
        /// <returns></returns>
        public static DataTable RecursiveSelectChildrenByKey(this DataTable pDataTable, string pRootValue, string pChildColName, string pParentColName, bool pAddNodeLevel, int pMaxDepth)
        {
            DataTable dtOut = pDataTable.Clone();
            if (pAddNodeLevel)
                dtOut.Columns.Add(new DataColumn("NodeLevel", typeof(Int32)));

            DataRow[] drChildren = pDataTable.Select(pChildColName + "='" + pRootValue + "'");
            if (drChildren.Length <= 0)
                return null;
            DataRow drNew = dtOut.LoadDataRow(drChildren[0].ItemArray, true);
            if (pAddNodeLevel)
                drNew["NodeLevel"] = 1;

            GetChildrenDown(pDataTable, pRootValue, pChildColName, pParentColName, 1, dtOut, pAddNodeLevel, pMaxDepth);
            dtOut.AcceptChanges();
            return dtOut;
        }

        /// <summary>
        /// Used to get recursively down
        /// </summary>
        /// <param name="pDataTable">DataTable object to be filtered</param>
        /// <param name="pRootFilter">Custom filter clause</param>
        /// <param name="pChildColName">Name of chiled column</param>
        /// <param name="pParentColName">Name of parent column</param>
        /// <param name="pAddNodeLevel">If set to <c>true</c> [p add node level].</param>
        /// <param name="pMaxDepth">Max depth</param>
        /// <returns></returns>
        public static DataTable RecursiveSelectChildrenByCustomFilter(this DataTable pDataTable, string pRootFilter, string pChildColName, string pParentColName, bool pAddNodeLevel, int pMaxDepth)
        {
            DataTable dtOut = pDataTable.Clone();
            if (pAddNodeLevel)
                dtOut.Columns.Add(new DataColumn("NodeLevel", typeof(Int32)));

            DataRow[] drChildren = pDataTable.Select(pRootFilter);
            if (drChildren.Length <= 0)
                return null;
            foreach (DataRow drRoot in drChildren)
            {
                DataRow drNew = dtOut.LoadDataRow(drRoot.ItemArray, true);
                if (pAddNodeLevel)
                    drNew["NodeLevel"] = 1;
                GetChildrenDown(pDataTable, drRoot[pChildColName].ToString(), pChildColName, pParentColName, 1, dtOut, pAddNodeLevel, pMaxDepth);
            }
            dtOut.AcceptChanges();
            return dtOut;
        }

        /// <summary>
        /// Used to get recursively up
        /// </summary>
        /// <param name="pDataTable">DataTable object to be filtered</param>
        /// <param name="pRootValue">Root value</param>
        /// <param name="pChildColName">Name of chiled column</param>
        /// <param name="pParentColName">Name of parent column</param>
        /// <param name="pAddNodeLevel">If set to <c>true</c> [p add node level].</param>
        /// <param name="pMaxDepth">Max depth</param>
        /// <returns></returns>
        public static DataTable RecursiveSelectParentsByKey(this DataTable pDataTable, string pChildValue, string pChildColName, string pParentColName, bool pAddNodeLevel, int pMaxDepth)
        {
            DataTable dtOut = pDataTable.Clone();
            if (pAddNodeLevel)
                dtOut.Columns.Add(new DataColumn("NodeLevel", typeof(Int32)));

            DataRow[] drChildren = pDataTable.Select(pChildColName + "='" + pChildValue + "'");
            if (drChildren.Length <= 0)
                return null;
            DataRow drNew = dtOut.LoadDataRow(drChildren[0].ItemArray, true);
            if (pAddNodeLevel)
                drNew["NodeLevel"] = 1;
            if (drChildren[0][pParentColName] != DBNull.Value)
                GetParentsUp(pDataTable, drChildren[0][pParentColName].ToString(), pChildColName, pParentColName, 1, dtOut, pAddNodeLevel, pMaxDepth);
            dtOut.AcceptChanges();
            return dtOut;
        }

        /// <summary>
        /// Used to get recursive path from a recursive table via unique key
        /// </summary>
        /// <param name="pDataSource">Source to queried datas</param>
        /// <param name="pKey">Unique key value</param>
        /// <param name="pTopKey">Top pivot unique key value for recursion process</param>
        /// <param name="pKeyName">Unique key column name</param>
        /// <param name="pUpKeyName">Up unique key column name</param>
        /// <param name="pPathColumnName">Column name to be get as path</param>
        /// <returns></returns>
        public static string GetRecursivePath(this DataTable pDataSource, int pKey, int? pTopKey, string pKeyName, string pUpKeyName, string pPathColumnName)
        {
            return GetRecursivePath(pDataSource, pKey, pTopKey, pKeyName, pUpKeyName, pPathColumnName, true, true);
        }

        /// <summary>
        /// Used to get recursive path from a recursive table via unique key
        /// </summary>
        /// <param name="pDataSource">Source to queried datas</param>
        /// <param name="pKey">Unique key value</param>
        /// <param name="pTopKey">Top pivot unique key value for recursion process</param>
        /// <param name="pKeyName">Unique key column name</param>
        /// <param name="pUpKeyName">Up unique key column name</param>
        /// <param name="pPathColumnName">Column name to be get as path</param>
        /// <param name="pAddBottomLevelItem">If set to true, valu which optained via pKey is added to path</param>
        /// <param name="pAddTopLevelItem">If set to true, valu which optained via pTopKey is added to path </param>
        /// <returns></returns>
        public static string GetRecursivePath(this DataTable pDataSource, int pKey, int? pTopKey, string pKeyName, string pUpKeyName, string pPathColumnName, bool pAddBottomLevelItem, bool pAddTopLevelItem)
        {
            DataTable oDt = pDataSource;
            if (oDt.Rows.Count > 0)
            {
                oDt.SetPrimaryKey(pKeyName);
                StringBuilder path = new StringBuilder();
                int topKey = Convert.ToInt32(pTopKey);
                int key = pKey;
                string pathItem = string.Empty;

                do
                {
                    DataRow row = oDt.Rows.Find(key);
                    if (row == null) break;
                    pathItem = row[pPathColumnName] != null ? row[pPathColumnName].ToString() : string.Empty;
                    path.Append(string.Format("/{0}", pathItem));
                    key = int.TryParse(row[pUpKeyName].ToString(), out key) == false ? 0 : Convert.ToInt32(row[pUpKeyName]);

                } while (key != topKey);

                DataRow rw = oDt.Rows.Find(topKey);
                if (rw != null)
                {
                    pathItem = rw[pPathColumnName] != null ? rw[pPathColumnName].ToString() : string.Empty;
                    path.Append(string.Format("/{0}", pathItem));
                }

                string result = path.ToString().TrimStart('/').InvertString('/');
                if (!pAddBottomLevelItem) result = result.Replace(result.RSplit('/'), string.Empty);
                if (!pAddTopLevelItem) result = result.Replace(result.LSplit('/'), string.Empty);
                return result.TrimStart('/').TrimEnd('/');
            }
            return string.Empty;
        }

        #region [Private Methods]

        private static void GetChildrenDown(DataTable pDataTable, string pRootValue, string pChildColName, string pParentColName, int pLevel, DataTable pOutTable, bool pAddNodeLevel, int pMaxDepth)
        {
            if (pMaxDepth < pLevel)
                return;

            DataRow[] drChildren = pDataTable.Select(pParentColName + "='" + pRootValue + "'");

            if (drChildren.Length <= 0)
                return;
            pLevel++;

            foreach (DataRow dr in drChildren)
            {
                DataRow drNew = pOutTable.LoadDataRow(dr.ItemArray, true);
                if (pAddNodeLevel)
                    drNew["NodeLevel"] = pLevel;
                GetChildrenDown(pDataTable, dr[pChildColName].ToString(), pChildColName, pParentColName, pLevel, pOutTable, pAddNodeLevel, pMaxDepth);
            }
        }

        private static void GetParentsUp(DataTable pDataTable, string pRootValue, string pChildColName, string pParentColName, int pLevel, DataTable pOutTable, bool pAddNodeLevel, int pMaxDepth)
        {
            if (pMaxDepth < pLevel)
                return;

            DataRow[] drChildren = pDataTable.Select(pChildColName + "='" + pRootValue + "'");

            if (drChildren.Length <= 0)
                return;
            pLevel++;

            foreach (DataRow dr in drChildren)
            {
                DataRow drNew = pOutTable.LoadDataRow(dr.ItemArray, true);
                if (pAddNodeLevel)
                    drNew["NodeLevel"] = pLevel;
                if (dr[pParentColName] != DBNull.Value)
                    GetParentsUp(pDataTable, dr[pParentColName].ToString(), pChildColName, pParentColName, pLevel, pOutTable, pAddNodeLevel, pMaxDepth);
            }
        }

        #endregion
    }
}